extends 基础场景类
class_name 棋局类

@onready var 棋盘: Node2D = %"棋盘"
@onready var board_tile_map: TileMapLayer = $"%BoardTileMap"
@onready var audio_stream_player: AudioStreamPlayer = $AudioStreamPlayer
@onready var 状态机: 基础树状态机类 = %"状态机"
@onready var 状态label = %"状态Label"
@onready var 回合信息 = %"回合信息"
@onready var 步数label: Label = %"步数Label"
@onready var 编号label: Label = %"编号Label"
@onready var 黑方label: Label = %"黑方Label"
@onready var 红方label: Label = %"红方Label"
@onready var 提示button: Button = %"提示Button"
@onready var 悔棋button: Button = %"悔棋Button"
@onready var 认输button: Button = %"认输Button"
@onready var 获取fen_button: Button = %"获取FENButton"
@onready var 获取moves_button: Button = %"获取movesButton"

#region 棋盘坐标相关
const 坐标字典:Dictionary={a=0,b=1,c=2,d=3,e=4,f=5,g=6,h=7,i=8}
const 字符坐标:String="abcdefghi"
const 棋盘max行:int=10
const 棋盘max列:int=9
#endregion

#region AI相关设定
var AI
var 红棋AI:int=0:
	set(value):
		红棋AI=value
		红方label.text=GameGlobal.AI名称[红棋AI]
var 黑棋AI:int=0:
	set(value):
		黑棋AI=value
		黑方label.text=GameGlobal.AI名称[黑棋AI]
var AI等待:float=0.5
#endregion

#region 棋局信息
var	红方行动:bool=false:
	set(value):
		红方行动=value
		刷新基础状态()

var moves:Array=[]
var 总步数:int=0:
	set(value):
		总步数=value
		刷新剩余步数()
var 当前回合数:int:
	get():
		return 总步数/2+1			
var 回合上限:int=-1:
	set(value):
		回合上限=value
		刷新剩余步数()

var 棋局FEN:String				#含moves信息
var 未吃子棋局FEN:String			#不含moves信息

var 棋子棋局:Array=[]:			#实际棋局, 供棋子判断能否吃子/将军等判断
	get():
		return 获取棋子棋局()
#endregion		

var 回合FEN数据:Array[回合数据类]=[]
signal 棋子移动完毕
signal 棋子被选中(棋子)
signal 请求提示
signal 请求悔棋
signal 请求认输
signal 悔棋完毕

func _ready() -> void:
	棋子移动完毕.connect(Callable(状态机,"当棋子移动完毕"))
	棋子被选中.connect(Callable(状态机,"当棋子被选中"))
	请求提示.connect(Callable(状态机,"当请求提示"))
	请求悔棋.connect(Callable(状态机,"当请求悔棋"))
	请求认输.connect(Callable(状态机,"当请求认输"))
	悔棋完毕.connect(Callable(状态机,"当悔棋完毕"))
	初始化AI()

#region 棋局初始化
func 初始化AI():
	AI=GameGlobal.AI
	红棋AI=GameGlobal.红棋AI
	黑棋AI=GameGlobal.黑棋AI
	AI.SendMessage.connect(OnAiSendMessage)
	AI.Start()	
	
	AI.SendCommand("uci")
	AI.SendCommand("setoption name Threads value "+str(OS.get_processor_count()))
	AI.SendCommand("setoption name EvalFile value pikafish.nnue")
	AI.SendCommand("setoption name UCI_ShowWDL value true")
	AI.SendCommand("setoption name Clear Hash")
	
func 初始化棋局数据():
	红方行动=false
	moves.clear()
	总步数=0
	棋局FEN=""
	未吃子棋局FEN=""
	
func 初始化棋局(棋谱数据:棋谱数据类):
	初始化棋局数据()
	载入棋局(棋谱数据)
	开始游戏()	
		
func 开始游戏():
	状态机.启动("初始")
		
func 暂停游戏():
	状态机.停止()
	
func 载入棋局(棋谱数据:棋谱数据类):
	回合上限=棋谱数据.红最长
	编号label.text=棋谱数据.id	
	载入FEN棋谱(棋谱数据.红先棋局)
	var 回合数据:回合数据类=回合数据类.new()
	回合数据.FEN=棋局FEN
	回合FEN数据.append(回合数据)
	
func 载入FEN棋谱(棋谱:String):
	棋谱=棋谱.replace("\n","")
	var 棋谱信息=棋谱.split(" ")
	var 棋子布局:String=棋谱信息[0]
	红方行动=(棋谱信息[1]=="w")
	var 字符棋局=棋谱转字符棋局(棋子布局)
	for 棋子 in board_tile_map.get_children():
		board_tile_map.remove_child(棋子)
		棋子.queue_free()
	for y in range(棋盘max行):
		for x in range(棋盘max列):
			if 字符棋局[y][x]!=null:
				var 棋子:棋子类=GameGlobal.FEN棋类[字符棋局[y][x]].instantiate()
				board_tile_map.add_child(棋子)	
				棋子.被选中.connect(当棋子被选中)
				棋子.棋局=self
				初始化棋子到pos(棋子,Vector2i(y,x))
	执行FEN中moves(棋谱)	
#endregion	
				
#region 指令获取  指令/坐标转换
#move指令为4位, 格式为g2h2
func 获取move指令(line:String):
	return line.left(13).right(4)

#指令为2位, 格式为g2
func 获取指令棋子(指令:String):
	var pos=获取指令坐标(指令)
	return 获取pos棋子(pos)	
	
#指令为2位, 格式为g2
func 获取指令坐标(指令:String):
	return Vector2i(9-int(指令[1]),坐标字典[指令[0]])
	
#指令为2位, 格式为g2	
func 坐标转指令(坐标:Vector2i):
	var 行:String=str(9-坐标.x)
	var 列:String=字符坐标[坐标.y]
	return  列+行
#endregion
		
#region 棋子初始化/移动相关	
func 初始化棋子到pos(棋子:棋子类,pos:Vector2i):
	棋子.pos=pos
	棋子.position=board_tile_map.map_to_local(Vector2i(pos.y,pos.x))		
	
#与移动棋子的区别是没有动画效果, 执行moves里的指令用
func 设置棋子到pos(棋子:棋子类,pos:Vector2i):
	红方行动=!红方行动
	棋子.position=board_tile_map.map_to_local(Vector2i(pos.y,pos.x))
	棋子落子(棋子,pos)
	
func 移动棋子到pos(move指令:String):
	var 棋子:棋子类=获取指令棋子(move指令.left(2))
	var pos:Vector2i=获取指令坐标(move指令.right(2))
	红方行动=!红方行动
	棋子.z_index=99	
	GameGlobal.播放移动音效()	
	棋盘.显示移动箭头(棋子.pos,pos)
	var tw=create_tween().set_ease(Tween.EASE_IN_OUT).set_trans(Tween.TRANS_LINEAR)
	tw.parallel().tween_property(棋子, "position", board_tile_map.map_to_local(Vector2i(pos.y,pos.x)), GameGlobal.动画速度)
	await tw.finished
	棋子.z_index=0
	棋子落子(棋子,pos)
	棋子移动完毕.emit()
		
func 棋子落子(棋子:棋子类,pos:Vector2i):
	if 可以吃子(pos):
		吃子(pos)
		moves.clear()
		棋子.pos=pos
		刷新吃子时棋局FEN()
	else:
		var move:String=坐标转指令(棋子.pos)+坐标转指令(pos)
		moves.append(move)
		刷新吃子后棋局FEN()
		棋子.pos=pos	
		
func 执行move指令(指令:String):
	var 棋子:棋子类=获取指令棋子(指令.left(2))
	var pos:Vector2i=获取指令坐标(指令.right(2))
	设置棋子到pos(棋子,pos)
		
func 执行FEN中moves(FEN:String):
	moves.clear()
	var FEN中moves=获取FEN中moves(FEN)
	if FEN中moves.is_empty():
		刷新吃子时棋局FEN()
	else:
		未吃子棋局FEN=获取FEN中棋局(FEN)
		for move in FEN中moves:
			执行move指令(move)
				
func 可以吃子(pos:Vector2i):
	var pos棋子:棋子类=获取pos棋子(pos)
	if pos棋子!=null:
		return true
	return false		

func 吃子(pos:Vector2i):
	GameGlobal.播放吃子音效()
	var pos棋子:棋子类=获取pos棋子(pos)
	board_tile_map.remove_child(pos棋子)
	pos棋子.queue_free()	

func 获取pos棋子(pos:Vector2i):
	return 棋子棋局[pos.x][pos.y]
	
func 刷新吃子时棋局FEN():
	未吃子棋局FEN=获取棋局FEN()
	棋局FEN=未吃子棋局FEN
	
func 刷新吃子后棋局FEN():
	棋局FEN=未吃子棋局FEN+获取moves字符串()	
#endregion

#region 刷新UI
func 显示状态(状态:String):
	if 红方行动:
		状态label.self_modulate=Color.DARK_RED
	else:
		状态label.self_modulate=Color.BLACK
	状态label.text=状态
	
func 刷新基础状态():
	if 红方行动:
		显示状态("红方行动")
	else:
		显示状态("黑方行动")
	
func 刷新剩余步数():
	if 回合上限==-1:
		步数label.text="无限"
	else:
		步数label.text=str(回合上限-当前回合数+1)
		
func 激活操作按钮():
	if 回合FEN数据.size()>1:
		悔棋button.disabled=false
	else:
		悔棋button.disabled=true	
	提示button.disabled=false
	获取fen_button.disabled=false
	获取moves_button.disabled=false
	认输button.disabled=false

func 停用操作按钮():
	悔棋button.disabled=true
	提示button.disabled=true
	获取fen_button.disabled=true
	获取moves_button.disabled=true
	认输button.disabled=true	
#endregion

#region 信号相关
func 当棋子被选中(棋子:棋子类):
	棋子被选中.emit(棋子)
#endregion

#region 棋局/FEN相关
func 获取棋局FEN()->String:
	var FEN:String=""
	var 字符棋局=获取字符棋局()
	for y in range(棋盘max行):
		var 可累加:bool=true
		var 空num:int=0
		if y>0:
			FEN+="/"
		for x in range(棋盘max列):
			var ch=字符棋局[y][x]
			if ch==null:
				可累加=true
				空num+=1
			else:
				if 空num>0:
					FEN+=str(空num)
				空num=0
				可累加=false
				FEN+=字符棋局[y][x]
			if x==棋盘max列-1 and 可累加: 
				FEN+=str(空num)
	var 当前方="b"
	if 红方行动:	当前方="w"		
	FEN+=" "+当前方+" - - 0 "+str(当前回合数)
	return FEN	

func 获取字符棋局():
	var _字符棋局=获取空棋局()
	for 棋子 in board_tile_map.get_children():
		var pos=棋子.pos
		_字符棋局[pos.x][pos.y]=棋子.ID
	return _字符棋局
	
func 获取棋子棋局():
	var _棋子棋局=获取空棋局()
	for 棋子 in board_tile_map.get_children():
		var pos=棋子.pos
		_棋子棋局[pos.x][pos.y]=棋子
	return _棋子棋局
	
func 棋谱转字符棋局(布局:String):
	var 字符棋局=获取空棋局()
	var now_x:int=0
	var now_y:int=0
	for ch in 布局:
		if ch>"0" and ch <="9":
			now_x+=int(ch)
		elif ch=="/":
			now_y+=1
			now_x=0
		else:
			字符棋局[now_y][now_x]=ch
			now_x+=1
	return 字符棋局
	
func 获取空棋局():
	var 空棋局棋子集:Array=[]
	for i in range(棋盘max行):
		var 一行:Array=[]
		一行.resize(棋盘max列)
		空棋局棋子集.append(一行)
	return 空棋局棋子集
	
func 获取moves字符串():
	var str_moves:String=" moves"
	for move in moves:
		str_moves+=(" "+move)
	return str_moves
	
func 获取FEN中moves(FEN:String):
	var FEN中moves:Array=[]
	var 指令集:Array=FEN.split(" ")
	var moves_index:int=指令集.find("moves")
	if moves_index!=-1:
		for index in range(moves_index+1,指令集.size()):
			FEN中moves.append(指令集[index])	
	return FEN中moves
	
func 获取FEN中棋局(FEN:String):
	var moves_index:int=FEN.find(" moves")
	if moves_index!=-1:
		return FEN.left(moves_index)
	return FEN
	
func 获取指令行棋信息(move指令:String):
	var 棋子:棋子类=获取指令棋子(move指令.left(2))
	var pos:Vector2i=获取指令坐标(move指令.right(2))
	return 棋子.获取行棋信息(pos)
#endregion

#region 棋局判定
func 和棋判定():
	if moves.size()<GameGlobal.和棋回合数:
		return false
	return true

func 红方可将军():
	var 黑将=获取老将("黑")
	for 棋子 in board_tile_map.get_children():
		if 棋子.棋子阵营=="红" and 棋子.可移动到pos(黑将.pos):
			return true	
	return false
	
func 黑方可将军():
	var 红将=获取老将("红")
	for 棋子 in board_tile_map.get_children():
		if 棋子.棋子阵营=="黑" and 棋子.可移动到pos(红将.pos):
			return true	
	return false
			
func 获取老将(阵营:String):
	for 棋子 in board_tile_map.get_children():
		if 棋子.棋子阵营==阵营 and 棋子 is 将类:
			return 棋子

func 老将对视():
	var 红将:棋子类=获取老将("红")
	var 黑将:棋子类=获取老将("黑")
	var 棋子数=红将.与目标之间棋子数(黑将.pos)
	if 棋子数==0:
		return true		
	return false
	
func 超过规定步数():
	return 回合上限!=-1 and 当前回合数>回合上限
#endregion

#region 状态机相关
func _input(event: InputEvent) -> void:
	状态机.on_input(event)
	
func OnAiSendMessage(line:String):
	状态机.OnAiSendMessage(line)
	
func _on_认输button_pressed() -> void:
	请求认输.emit()

func _on_提示button_pressed() -> void:
	请求提示.emit()
#endregion

func _on_悔棋button_pressed() -> void:
	悔棋()
	
func 悔棋():
	暂停游戏()
	停用操作按钮()
	棋盘.清除移动箭头()
	棋盘.清除提示箭头()
	初始化棋局数据()
	回合FEN数据.pop_back()
	var fen数据=回合FEN数据.back()
	总步数=回合FEN数据.size()-1
	回合信息.悔棋()
	载入FEN棋谱(fen数据.FEN)
	if fen数据.指令!="":
		var start_pos:Vector2i=获取指令坐标(fen数据.指令.left(2))
		var stop_pos:Vector2i=获取指令坐标(fen数据.指令.right(2))
		移动棋子到pos(fen数据.指令)
		棋盘.显示移动箭头(start_pos,stop_pos)
		await 棋子移动完毕
		
	if 回合FEN数据.size()==1 or (红方行动 and 红棋AI==0) or (not 红方行动 and 黑棋AI==0):
		开始游戏()
	else:
		悔棋button.disabled=false
		获取fen_button.disabled=false
		获取moves_button.disabled=false
	
func _on_获取fen_button_pressed() -> void:
	GameViewManager.打开弹窗("获取FEN",{FEN=获取棋局FEN()})

func _on_获取moves_button_pressed() -> void:
	GameViewManager.打开弹窗("获取FEN",{FEN=棋局FEN})
